/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package simhya.simengine.hybrid;

import simhya.dataprocessing.HybridDataCollector;
import simhya.model.flat.FlatModel;
import org.apache.commons.math.ode.events.EventHandler;
import simhya.simengine.SimulationException;
import simhya.simengine.hybrid.globalruleparser.SwitchRuleParser;
import java.util.ArrayList;
import java.util.Arrays;

/**
 *
 * @author luca
 */
public class HybridSimulatorWithDynamicSwitching extends AbstractHybridSimulator {
    GlobalSwitchType switchType;
    SwitchRuleParser parser;
    double discrete2continuousThreshold, continuous2discreteThreshold;

    
    
    public HybridSimulatorWithDynamicSwitching(FlatModel model, HybridDataCollector collector, 
            boolean switching) {
        super(model, collector, true);
        if (switching) {
            this.switchType = GlobalSwitchType.RULES;
            initializePartitionManager();
            parser = new SwitchRuleParser(model.getStore(),this.eventSwitcher);
        }
        else {
            this.switchType = GlobalSwitchType.NO_SWITCH;
            initializePartitionManager();
            parser = null;
        }
    }
    
    /**
     * Instantiates a new hybrid simulator with global partitioning based on population levels.
     * @param model
     * @param collector
     * @param discrete2continuousThreshold
     * @param continuous2discreteThreshold 
     */
    public HybridSimulatorWithDynamicSwitching(FlatModel model, HybridDataCollector collector, 
            double discrete2continuousThreshold, double continuous2discreteThreshold) {
        super(model, collector, true);
        this.switchType = GlobalSwitchType.POPULATION;
        this.discrete2continuousThreshold = discrete2continuousThreshold;
        this.continuous2discreteThreshold = continuous2discreteThreshold;
        initializePartitionManager();
        parser = new SwitchRuleParser(model.getStore(),this.eventSwitcher);
    }
    
    
    public void addGlobalSwitchingRule(String rule) {
        try { parser.parseFromString(rule); }
        catch (Exception e) {
            throw new SimulationException("Error while parsing global rule: " + e.getMessage());
        }
        catch (Error e) {
            throw new SimulationException("Error while parsing global rule: " + e.getMessage());
        }
    }
    
    /**
     * Adds a set of global switching rules from 
     * @param filename 
     */
    public void addGlobalSwitchingRuleFromFile(String filename) {
        try { parser.parseFromFile(filename); }
        catch (Exception e) {
            throw new SimulationException("Error while parsing global rule: " + e.getMessage());
        }
        catch (Error e) {
            throw new SimulationException("Error while parsing global rule: " + e.getMessage());
        }
    }

    @Override
    void initializeDependencyGraphs() {
        for (EventHandler e : this.events) {
            int id = e.getId();
            java.util.HashSet<Integer> updated = new java.util.HashSet<Integer>();
            if (id >= 0) {
                //updated triggers. check guards of all transitions plus firint times.
                //add switched depending on updated variables.
                Integer[] upGuard = model.getListOfUpdatedGuards(id);
                for (Integer i : upGuard)
                    if (this.eventIds.contains(i))
                        updated.add(i);
                Integer[] upFT = model.getListOfUpdatedFiringTimes(id);
                for (Integer i : upFT)
                    if (this.eventIds.contains(i))
                        updated.add(i);
                ArrayList<Integer> upV = model.getTransition(id).getUpdatedVariables();
                //add modified switching events
                updated.addAll(eventSwitcher.findEventsAffectedByVariables(upV));
                e.setIdsOfEventsWithModifiedTrigger(updated);
                //updated priorities of inst transitions.
                updated = new java.util.HashSet<Integer>();
                model.getListOfUpdatedRates(id);
                java.util.HashSet<Integer> instTr = new java.util.HashSet<Integer>();
                for (Integer j : model.getListOfInstantaneousTransitionsID())
                    instTr.add(j);
                for (Integer i : model.getListOfUpdatedRates(id)) 
                    if (instTr.contains(i))
                        updated.add(i);
                e.setIdsOfEventsWithModifiedPriority(updated);
            } else {
                e.setIdsOfEventsWithModifiedTrigger(new ArrayList<Integer>());
                e.setIdsOfEventsWithModifiedPriority(new ArrayList<Integer>());
            }
        }
        this.depGraphInitialized = true;
    }
    
    
    
    
    
    void initializePartitionManager() {
        switch(this.switchType) {
            case NO_SWITCH:
                this.eventSwitcher = null;
                break;
            case RULES:
                this.eventSwitcher = new PartitionManager(model,function);
                break;
            case POPULATION:
                this.eventSwitcher = new PopulationSizePartitionManager(model,function,
                        this.discrete2continuousThreshold,this.continuous2discreteThreshold);
                break;
            case RATE:
            case POPULATION_AND_RATE:
                throw new SimulationException("Global partitioning strategy " + 
                        switchType.toString() + "  not yet implemented");
            default:
                throw new SimulationException("Unknown partitioning strategy");
        }  
    }
    
    
    private void addEventHandlers() {
        for (EventHandler e : super.events) {
            integrator.addEventHandler(e, maxStepIncrementForEvents,
                    maxErrorForEvents, maxIterationforEvents);
        }
        if (eventSwitcher != null) {
            for (EventHandler e : eventSwitcher.getEventList()) {
                integrator.addEventHandler(e, maxStepIncrementForEvents,
                        maxErrorForEvents, maxIterationforEvents);
            }
        }
    }

    public void initialize() {
        if (!this.depGraphInitialized)
            initializeDependencyGraphs();
        integrator = super.newIntegrator();
        integrator.addStepHandler(this);
        //add event handlers.
        addEventHandlers();
        initialized = true;
    }

    public void reinitialize() {
        if (!this.depGraphInitialized)
            initializeDependencyGraphs();
        integrator = super.newIntegrator();
        integrator.addStepHandler(this);
        //add event handlers.
        addEventHandlers();
        initialized = true;
    }
    
}
